/*******************************************************************************
 *                                    ZLG
 *                         ----------------------------
 *                         innovating embedded platform
 *
 * Copyright (c) 2001-present Guangzhou ZHIYUAN Electronics Co., Ltd.
 * All rights reserved.
 *
 * Contact information:
 * web site:    https://www.zlg.cn
 *******************************************************************************/
/*******************************************************************************
 * Includes
 ******************************************************************************/
#include "driver/qspi/hc32f4a0_qspi_cfg.h"
#include "driver/qspi/hc32f4a0_qspi.h"
#include "common/hc32f4a0_errno.h"
#include "core/include/hc32f4a0_pwc.h"
#include "core/include/hc32f4a0_clk.h"

/*******************************************************************************
 * Macro operate
 ******************************************************************************/
/* \brief 把一个4字节数据转成4个1字节数组*/
#define QSPI_WORD_TO_BYTE(__word__, __au8__)                  \
        do {                                                  \
                (__au8__)[2U] = (uint8_t)(__word__);          \
                (__au8__)[1U] = (uint8_t)((__word__) >> 8U);  \
                (__au8__)[0U] = (uint8_t)((__word__) >> 16U); \
                (__au8__)[3U] = (uint8_t)((__word__) >> 24U); \
        } while (0U)

/*******************************************************************************
 * Code
 ******************************************************************************/
/**
 * \brief QSPI 设置读模式
 */
void QSPI_ReadModeSet(uint32_t ReadMode, uint8_t ReadInstr, uint32_t DummyCycles){

    if ((!IS_QSPI_READ_MODE(ReadMode)) && (!IS_QSPI_DUMMY_CYCLES(DummyCycles))) {
        return;
    }

    MODIFY_REG32(HC32F4A0_QSPI->FCR, QSPI_FCR_DMCYCN, (DummyCycles - 3) << QSPI_FCR_DMCYCN_POS);
    MODIFY_REG32(HC32F4A0_QSPI->CR, QSPI_CR_MDSEL, ReadMode);
    WRITE_REG32(HC32F4A0_QSPI->CCMD, ReadInstr);
}

/**
 * \brief QSPI 读数据
 *
 * \param[in] address   要读的地址
 * \param[in] p_dest    数据缓存
 * \param[in] dest_size 要读的数据大小
 *
 * \retval 成功返回0
 */
int qspi_data_read(uint32_t address, uint8_t *p_dest, uint32_t dest_size){
    uint32_t i   = 0;
    int      ret = -EINVAL;

    if ((p_dest != NULL) && (dest_size > 0)) {
        address += QSPI_ROM_MAP_BASE;
        while (dest_size-- != 0) {
            p_dest[i++] = *(uint8_t *)(address++);
        }

        ret = 0;
    }

    return ret;
}

/**
 * \brief QSPI 写数据
 *
 * \param[in] instr    指令
 * \param[in] address  要写的地址
 * \param[in] p_src    数据缓存
 * \param[in] size     要写的数据大小
 *
 * \retval 成功返回0
 */
int qspi_data_write(uint32_t       instr,
                    uint32_t       address,
                    const uint8_t *p_src,
                    uint32_t       size){
    uint32_t i;
    uint8_t  address_tmp[4];
    uint32_t addr_width;
    int      ret = -EINVAL;

    if ((p_src != NULL) && (size > 0)) {
        QSPI_WORD_TO_BYTE(address, address_tmp);
        addr_width = (READ_REG32_BIT(HC32F4A0_QSPI->FCR, QSPI_FCR_AWSL) >> QSPI_FCR_AWSL_POS) + 1;

        /* 进入直接通讯模式 */
        qspi_direct_comm_mode_enter();
        /* 设置指令 */
        WRITE_REG32(HC32F4A0_QSPI->DCOM, instr);
        /* 发送 ROM 地址 */
        for (i = 0; i < addr_width; i++) {
            WRITE_REG32(HC32F4A0_QSPI->DCOM, address_tmp[i]);
        }
        /* 最后写数据 */
        for (i = 0; i < size; i++) {
            WRITE_REG32(HC32F4A0_QSPI->DCOM, p_src[i]);
        }
        /* 退出直接通讯模式 */
        qspi_direct_comm_mode_exit();

        ret = 0;
    }

    return ret;
}

/**
 * \brief QSPI 结构体初始化
 */
void qspi_struct_init(struct hc32f4a0_qspi_cfg *p_qspi_cfg){
    if (p_qspi_cfg == NULL) {
        return;
    }

    p_qspi_cfg->clk_div           = 2UL;
    p_qspi_cfg->cs_setup_timing   = QSPI_CS_SETUP_BEFORE_0P5_CYCLE;
    p_qspi_cfg->cs_release_timing = QSPI_CS_RELEASE_AFTER_0P5_CYCLE;
    p_qspi_cfg->cs_idle_time      = 1UL;
    p_qspi_cfg->cs_extend_time    = QSPI_CS_EXTEND_0CYCLE;
    p_qspi_cfg->spi_mode          = QSPI_SPI_MODE_0;
    p_qspi_cfg->prefetch_stop_pos = QSPI_PREFETCH_STOP_BYTE_EDGE;
    p_qspi_cfg->prefetch_cmd      = QSPI_PREFETCH_ENABLE;
    p_qspi_cfg->wp_level          = QSPI_WP_HIGH;
    p_qspi_cfg->comm_mode         = QSPI_COMM_ROM_ACCESS;
    p_qspi_cfg->addr_width        = QSPI_ADDR_WIDTH_3BYTE;
    p_qspi_cfg->instr_mode        = QSPI_INSTR_1LINE;
    p_qspi_cfg->addr_mode         = QSPI_ADDR_1LINE;
    p_qspi_cfg->data_mode         = QSPI_DATA_1LINE;
    p_qspi_cfg->read_mode         = QSPI_READ_STANDARD_READ;
    p_qspi_cfg->rom_access_instr  = 0x0U;
    p_qspi_cfg->dummy_cycles      = 3UL;
}

/**
 * \brief QSPI 时钟初始化
 */
static int qspi_clk_init(void){
    return pwc_fcg1_periph_clk_set(PWC_FCG1_QSPI, PWC_ENABLE);
}

/**
 * \brief 初始化 QSPI GPIO
 */
static int qspi_gpio_init(struct qspi_drv_cfg *p_qspi_drv_cfg){
    int                      ret = 0;
    struct hc32f4a0_gpio_cfg cfg;

    gpio_unlock();
    gpio_struct_init(&cfg);
    cfg.pull_up = PIN_PU_ON;
    cfg.pin_drv = PIN_DRV_HIGH;
    ret = gpio_cfg(p_qspi_drv_cfg->io0_port,
                  (p_qspi_drv_cfg->io0_pin |
                   p_qspi_drv_cfg->io1_pin |
                   p_qspi_drv_cfg->io2_pin |
                   p_qspi_drv_cfg->io3_pin),
                  &cfg);
    if (ret != 0) {
        goto __end;
    }
    /* 设置 QSPI IO0 引脚功能*/
    ret = gpio_func_set(p_qspi_drv_cfg->io0_port,
                        p_qspi_drv_cfg->io0_pin,
                        p_qspi_drv_cfg->io0_func,
                        GPIO_PIN_SUBFUN_DISABLE);
    if (ret != 0) {
        goto __end;
    }
    /* 设置 QSPI IO1 引脚功能*/
    ret = gpio_func_set(p_qspi_drv_cfg->io1_port,
                        p_qspi_drv_cfg->io1_pin,
                        p_qspi_drv_cfg->io1_func,
                        GPIO_PIN_SUBFUN_DISABLE);
    if (ret != 0) {
        goto __end;
    }
    /* 设置 QSPI IO2 引脚功能*/
    ret = gpio_func_set(p_qspi_drv_cfg->io2_port,
                        p_qspi_drv_cfg->io2_pin,
                        p_qspi_drv_cfg->io2_func,
                        GPIO_PIN_SUBFUN_DISABLE);
    if (ret != 0) {
        goto __end;
    }
    /* 设置 QSPI IO3 引脚功能*/
    ret = gpio_func_set(p_qspi_drv_cfg->io3_port,
                        p_qspi_drv_cfg->io3_pin,
                        p_qspi_drv_cfg->io3_func,
                        GPIO_PIN_SUBFUN_DISABLE);
    if (ret != 0) {
        goto __end;
    }
    /* 设置 QSPI NSS 引脚功能*/
    ret = gpio_func_set(p_qspi_drv_cfg->nss_port,
                        p_qspi_drv_cfg->nss_pin,
                        p_qspi_drv_cfg->nss_func,
                        GPIO_PIN_SUBFUN_DISABLE);
    if (ret != 0) {
        goto __end;
    }
    /* 设置 QSPI SCK 引脚功能*/
    ret = gpio_func_set(p_qspi_drv_cfg->sck_port,
                        p_qspi_drv_cfg->sck_pin,
                        p_qspi_drv_cfg->sck_func,
                        GPIO_PIN_SUBFUN_DISABLE);
    if (ret != 0) {
        goto __end;
    }
__end:
    gpio_lock();

    return ret;
}

/**
 * \brief 初始化 QSPI 配置
 */
int qspi_cfg(struct hc32f4a0_qspi_cfg *p_qspi_cfg){
    uint32_t DutyCorrection = 0;

    if (p_qspi_cfg == NULL) {
        return -EINVAL;
    }

    if ((!IS_QSPI_CLK_DIV(p_qspi_cfg->clk_div)) ||
            (!IS_QSPI_CS_SETUP_TIMING(p_qspi_cfg->cs_setup_timing)) ||
            (!IS_QSPI_CS_RELEASE_TIMING(p_qspi_cfg->cs_release_timing)) ||
            (!IS_QSPI_CS_IDLE_TIME(p_qspi_cfg->cs_idle_time)) ||
            (!IS_QSPI_CS_EXTEND_TIME(p_qspi_cfg->cs_extend_time)) ||
            (!IS_QSPI_SPI_MODE(p_qspi_cfg->spi_mode)) ||
            (!IS_QSPI_PREFETCH_STOP_POSITION(p_qspi_cfg->prefetch_stop_pos)) ||
            (!IS_QSPI_PREFETCH_CMD(p_qspi_cfg->prefetch_cmd)) ||
            (!IS_QSPI_WP_LEVEL(p_qspi_cfg->wp_level)) ||
            (!IS_QSPI_READ_MODE(p_qspi_cfg->read_mode)) ||
            (!IS_QSPI_COMM_MODE(p_qspi_cfg->comm_mode)) ||
            (!IS_QSPI_ADDR_WIDTH(p_qspi_cfg->addr_width)) ||
            (!IS_QSPI_DUMMY_CYCLES(p_qspi_cfg->dummy_cycles)) ||
            (!IS_QSPI_INSTR_MODE(p_qspi_cfg->instr_mode)) ||
            (!IS_QSPI_ADDR_MODE(p_qspi_cfg->addr_mode)) ||
            (!IS_QSPI_DATA_MODE(p_qspi_cfg->data_mode))) {
        return -EINVAL;
    }

    if ((p_qspi_cfg->clk_div & 1) != 0) {
        DutyCorrection = QSPI_FCR_DUTY;
    }

    WRITE_REG32(HC32F4A0_QSPI->CR, (((p_qspi_cfg->clk_div - 1) << QSPI_CR_DIV_POS) |
                                      p_qspi_cfg->spi_mode                         |
                                      p_qspi_cfg->prefetch_stop_pos                |
                                      p_qspi_cfg->prefetch_cmd                     |
                                      p_qspi_cfg->read_mode                        |
                                      p_qspi_cfg->comm_mode                        |
                                      p_qspi_cfg->instr_mode                       |
                                      p_qspi_cfg->addr_mode                        |
                                      p_qspi_cfg->data_mode));

    WRITE_REG32(HC32F4A0_QSPI->CSCR, (p_qspi_cfg->cs_extend_time  |
                                     (p_qspi_cfg->cs_idle_time - 1)));

    WRITE_REG32(HC32F4A0_QSPI->FCR, (p_qspi_cfg->cs_setup_timing                             |
                                     p_qspi_cfg->cs_release_timing                           |
                                     p_qspi_cfg->addr_width                                  |
                                   ((p_qspi_cfg->dummy_cycles - 3UL) << QSPI_FCR_DMCYCN_POS) |
                                     p_qspi_cfg->wp_level                                    |
                                     DutyCorrection));

   WRITE_REG32(HC32F4A0_QSPI->CCMD, p_qspi_cfg->rom_access_instr);

   return 0;
}

/**
 * \brief 初始化 QSPI
 */
int qspi_drv_init(void){
    int                      ret;
    struct hc32f4a0_qspi_cfg cfg;

    qspi_struct_init(&cfg);

    ret = qspi_clk_init();
    if (ret != 0) {
        return ret;
    }

    ret = qspi_gpio_init(&qspi_drv_config);
    if (ret != 0) {
        return ret;
    }

    return qspi_cfg(&cfg);
}
